 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.ide.undo;



 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.ui.internal.ide.undo.UndoMessages;

 /**
  * An AbstractCopyOrMoveResourcesOperation represents an undoable operation for
  * moving or copying one or more resources in the workspace. Clients may call
  * the public API from a background thread.
  *
  * This class is not intended to be subclassed by clients.
  *
  * @since 3.3
  *
  */
 abstract class AbstractCopyOrMoveResourcesOperation extends
         AbstractResourcesOperation {

     // Used when there are different destination names for each resource
 protected IPath[] destinationPaths = null;

     // Used when all resources are going to the same container (no name changes)
 protected IPath destination = null;

     /**
      * Create an AbstractCopyOrMoveResourcesOperation that moves or copies all
      * of the specified resources to the specified paths. The destination paths
      * must include the names of the resources at their new location.
      *
      * @param resources
      * the resources to be moved or copied. May not contain null
      * resources, or resources that are descendants of already
      * included resources.
      * @param destinationPaths
      * the destination paths for the resources, including the name to
      * be assigned to the resource at its new location. May not contain
      * null paths, and must be the same length as the resources array.
      * @param label
      * the label of the operation
      *
      */
     AbstractCopyOrMoveResourcesOperation(IResource[] resources,
             IPath[] destinationPaths, String label) {
         super(resources, label);
         // Check for null arguments
 if (this.resources == null || destinationPaths == null)
             throw new IllegalArgumentException ("The resource and destination paths may not be null"); //$NON-NLS-1$
 // Special case to flag descendants. Note this would fail on the next check
 // anyway, so we are first giving a more specific explanation.
 // See bug #176764
 if (this.resources.length != resources.length)
             throw new IllegalArgumentException ("The resource list contained descendants that cannot be moved to separate destination paths"); //$NON-NLS-1$
 // Check for destination paths corresponding for each resource
 if (this.resources.length != destinationPaths.length) {
             throw new IllegalArgumentException ("The resource and destination paths must be the same length"); //$NON-NLS-1$
 }
         for (int i=0; i<this.resources.length; i++) {
             if (this.resources[i] == null) {
                 throw new IllegalArgumentException ("The resources array may not contain null resources"); //$NON-NLS-1$
 }
             if (destinationPaths[i] == null) {
                 throw new IllegalArgumentException ("The destination paths array may not contain null paths"); //$NON-NLS-1$
 }
         }
         this.destinationPaths = destinationPaths;
     }

     /**
      * Create an AbstractCopyOrMoveResourcesOperation that moves or copies all
      * of the specified resources to the same target location, using their
      * existing names.
      *
      * @param resources
      * the resources to be moved or copied
      * @param destinationPath
      * the destination path for the resources, not including the name
      * of the new resource.
      * @param label
      * the label of the operation
      */
     AbstractCopyOrMoveResourcesOperation(IResource[] resources,
             IPath destinationPath, String label) {
         super(resources, label);
         destination = destinationPath;
     }

     /**
      * Create an AbstractCopyOrMoveResourcesOperation whose destination is not
      * yet specified.
      *
      * @param resources
      * the resources to be modified
      * @param label
      * the label of the operation
      */
     AbstractCopyOrMoveResourcesOperation(IResource[] resources, String label) {
         super(resources, label);
     }


     /**
      * Compute the status for moving or copying the resources. A status severity
      * of <code>OK</code> indicates that the copy or move is likely to be
      * successful. A status severity of <code>ERROR</code> indicates that the
      * operation is no longer valid. Other status severities are open to
      * interpretation by the caller.
      *
      * Note this method may be called on initial moving or copying of a
      * resource, or when a move or copy is undone or redone. Therefore, this
      * method should check conditions that can change over the life of the
      * operation, such as whether the file to moved or copied exists, and
      * whether the target location is still valid. One-time static checks should
      * typically be done by the caller so that the user is not continually
      * prompted or warned about conditions that were acceptable at the time of
      * original execution and do not change over time.
      *
      * @return the status indicating the projected outcome of moving or copying
      * the resources.
      */
     protected IStatus computeMoveOrCopyStatus() {
         // Check for error conditions first so that we do not prompt the user
 // on warnings that eventually will not matter anyway.
 if (resources == null) {
             markInvalid();
             return getErrorStatus(UndoMessages.AbstractResourcesOperation_NotEnoughInfo);
         }
         for (int i = 0; i < resources.length; i++) {
             IResource resource = resources[i];
             // Does the resource still exist?
 if (!resource.exists()) {
                 markInvalid();
                 return getErrorStatus(UndoMessages.AbstractCopyOrMoveResourcesOperation_ResourceDoesNotExist);
             }

             // Are we really trying to move it to a different name?
 if (!isDestinationPathValid(resource, i)) {
                 markInvalid();
                 return getErrorStatus(UndoMessages.AbstractCopyOrMoveResourcesOperation_SameNameOrLocation);
             }
             // Is the proposed name valid?
 IStatus status = getWorkspace().validateName(
                     getProposedName(resource, i), resource.getType());
             if (status.getSeverity() == IStatus.ERROR) {
                 markInvalid();
             }
             if (!status.isOK()) {
                 return status;
             }
         }
         return Status.OK_STATUS;
     }

     /**
      * Return the destination path that should be used to move or copy the
      * specified resource. This path is relative to the workspace.
      *
      * @param resource
      * the resource being moved or copied
      * @param index
      * the integer index of the resource in the resource array
      * @return the path specifying the destination for the resource
      */
     protected IPath getDestinationPath(IResource resource, int index) {
         if (destinationPaths != null) {
             return destinationPaths[index];
         }
         return destination.append(resource.getName());

     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#appendDescriptiveText(java.lang.StringBuffer)
      */
     protected void appendDescriptiveText(StringBuffer text) {
         super.appendDescriptiveText(text);
         text.append(" destination: "); //$NON-NLS-1$
 text.append(destination);
         text.append(", destinationPaths: "); //$NON-NLS-1$
 text.append(destinationPaths);
         text.append('\'');
     }

     /**
      * Return a boolean indicating whether the proposed destination path for a
      * resource is valid.
      *
      * @param resource
      * the resource whose path is to be checked
      * @param index
      * the integer index of the resource in the resource array
      * @return a boolean indicating whether the destination path is valid
      */
     protected boolean isDestinationPathValid(IResource resource, int index) {
         return !resource.getFullPath().equals(
                 getDestinationPath(resource, index));
     }

     /**
      * Return a string indicating the proposed name for the resource
      *
      * @param resource
      * the resource whose path is to be checked
      * @param index
      * the integer index of the resource in the resource array
      * @return the string name of the resource
      */
     protected String getProposedName(IResource resource, int index) {
         return getDestinationPath(resource, index).lastSegment();
     }

     /*
      * (non-Javadoc)
      *
      * Map execution to move status.
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeExecutionStatus(org.eclipse.core.runtime.IProgressMonitor)
      */
     public IStatus computeExecutionStatus(IProgressMonitor monitor) {
         IStatus status = super.computeExecutionStatus(monitor);
         if (status.isOK()) {
             status = computeMoveOrCopyStatus();
         }
         return status;
     }

     /*
      * (non-Javadoc)
      *
      * Map redo to move status.
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeRedoableStatus(org.eclipse.core.runtime.IProgressMonitor)
      */
     public IStatus computeRedoableStatus(IProgressMonitor monitor) {
         IStatus status = super.computeRedoableStatus(monitor);
         if (status.isOK()) {
             status = computeMoveOrCopyStatus();
         }
         return status;
     }
 }

